/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.replica.replicaisland;

import com.replica.replicaisland.CollisionParameters.HitType;
import com.replica.replicaisland.GameObject.ActionType;
import com.replica.replicaisland.SoundSystem.Sound;

/**
 * Player Animation game object component. Responsible for selecting an
 * animation to describe the player's current state. Requires the object to
 * contain a SpriteComponent to play animations.
 */
public class AnimationComponent extends GameComponent {

	public enum PlayerAnimations {
		IDLE, MOVE, MOVE_FAST, BOOST_UP, BOOST_MOVE, BOOST_MOVE_FAST, STOMP, HIT_REACT, DEATH, FROZEN
	}

	private static final float MIN_ROCKET_TIME = 0.0f;
	private static final float FLICKER_INTERVAL = 0.15f;
	private static final float FLICKER_DURATION = 3.0f;
	private static final float LAND_THUMP_DELAY = 0.5f;

	private SpriteComponent mSprite;
	private SpriteComponent mJetSprite;
	private SpriteComponent mSparksSprite;

	private PlayerComponent mPlayer;
	private float mLastFlickerTime;
	private boolean mFlickerOn;
	private float mFlickerTimeRemaining;

	private ActionType mPreviousAction;

	private float mLastRocketsOnTime;
	private boolean mExplodingDeath;

	private ChangeComponentsComponent mDamageSwap;
	private Sound mLandThump;
	private Sound mRocketSound;
	private Sound mExplosionSound;
	private float mLandThumpDelay;
	private int mRocketSoundStream;
	private boolean mRocketSoundPaused;

	private int mLastRubyCount;
	private Sound mRubySound1;
	private Sound mRubySound2;
	private Sound mRubySound3;
	private InventoryComponent mInventory;

	public AnimationComponent() {
		super();
		reset();
		setPhase(ComponentPhases.ANIMATION.ordinal());
	}

	@Override
	public void reset() {
		mPreviousAction = ActionType.INVALID;
		mSprite = null;
		mJetSprite = null;
		mSparksSprite = null;
		mPlayer = null;
		mLastFlickerTime = 0.0f;
		mFlickerOn = false;
		mFlickerTimeRemaining = 0.0f;
		mLastRocketsOnTime = 0.0f;
		mExplodingDeath = false;
		mDamageSwap = null;
		mLandThump = null;
		mLandThumpDelay = 0.0f;
		mRocketSound = null;
		mRocketSoundStream = -1;
		mLastRubyCount = 0;
		mInventory = null;
		mExplosionSound = null;
	}

	@Override
	public void update(float timeDelta, BaseObject parent) {
		if (mSprite != null) {

			GameObject parentObject = (GameObject) parent;

			final float velocityX = parentObject.getVelocity().x;
			final float velocityY = parentObject.getVelocity().y;

			ActionType currentAction = parentObject
					.getCurrentAction();

			if (mJetSprite != null) {
				mJetSprite.setVisible(false);
			}

			if (mSparksSprite != null) {
				mSparksSprite.setVisible(false);
			}

			final TimeSystem time = sSystemRegistry.timeSystem;
			final float gameTime = time.getGameTime();

			if (currentAction != ActionType.HIT_REACT
					&& mPreviousAction == ActionType.HIT_REACT) {
				mFlickerTimeRemaining = FLICKER_DURATION;
			}

			final boolean touchingGround = parentObject.touchingGround();

			boolean boosting = mPlayer != null ? mPlayer.getRocketsOn() : false;

			boolean visible = true;

			SoundSystem sound = sSystemRegistry.soundSystem;

			// It's usually not necessary to test to see if sound is enabled or
			// not (when it's disabled,
			// play() is just a nop), but in this case I have a stream that is
			// maintained for the rocket
			// sounds. So it's simpler to just avoid that code if sound is off.
			if (sound.getSoundEnabled()) {
				if (boosting) {
					mLastRocketsOnTime = gameTime;
				} else {
					if (gameTime - mLastRocketsOnTime < MIN_ROCKET_TIME
							&& velocityY >= 0.0f) {
						boosting = true;
					}
				}

				if (mRocketSound != null) {
					if (boosting) {
						if (mRocketSoundStream == -1) {
							mRocketSoundStream = sound.play(mRocketSound, true,
									SoundSystem.PRIORITY_HIGH);
							mRocketSoundPaused = false;
						} else if (mRocketSoundPaused) {
							sound.resume(mRocketSoundStream);
							mRocketSoundPaused = false;
						}
					} else {
						sound.pause(mRocketSoundStream);
						mRocketSoundPaused = true;
					}
				}
			}

			// Normally, for collectables like the coin, we could just tell the
			// object to play
			// a sound when it is collected. The gems are a special case,
			// though, as we
			// want to pick a different sound depending on how many have been
			// collected.
			if (mInventory != null && mRubySound1 != null
					&& mRubySound2 != null && mRubySound3 != null) {
				InventoryComponent.UpdateRecord inventory = mInventory
						.getRecord();
				final int rubyCount = inventory.rubyCount;
				if (rubyCount != mLastRubyCount) {
					mLastRubyCount = rubyCount;
					switch (rubyCount) {
					case 1:
						sound.play(mRubySound1, false,
								SoundSystem.PRIORITY_NORMAL);
						break;
					case 2:
						sound.play(mRubySound2, false,
								SoundSystem.PRIORITY_NORMAL);
						break;
					case 3:
						sound.play(mRubySound3, false,
								SoundSystem.PRIORITY_NORMAL);
						break;
					}

				}
			}

			// Turn on visual effects (smoke, etc) when the player's life
			// reaches 1.
			if (mDamageSwap != null) {
				if (parentObject.life == 1
						&& !mDamageSwap.getCurrentlySwapped()) {
					mDamageSwap.activate(parentObject);
				} else if (parentObject.life != 1
						&& mDamageSwap.getCurrentlySwapped()) {
					mDamageSwap.activate(parentObject);
				}
			}

			float opacity = 1.0f;

			if (currentAction == ActionType.MOVE) {
				InputGameInterface input = sSystemRegistry.inputGameInterface;
				final InputXY dpad = input.getDirectionalPad();
				if (dpad.getX() < 0.0f) {
					parentObject.facingDirection.x = -1.0f;
				} else if (dpad.getX() > 0.0f) {
					parentObject.facingDirection.x = 1.0f;
				}

				// TODO: get rid of these magic numbers!
				if (touchingGround) {

					if (Utils.close(velocityX, 0.0f, 30.0f)) {
						mSprite.playAnimation(PlayerAnimations.IDLE.ordinal());
					} else if (Math.abs(velocityX) > 300.0f) {
						mSprite.playAnimation(PlayerAnimations.MOVE_FAST
								.ordinal());
					} else {
						mSprite.playAnimation(PlayerAnimations.MOVE.ordinal());
					}

					final InputButton attackButton = input.getAttackButton();

					if (attackButton.getPressed()) {
						// charge
						final float pressedTime = gameTime
								- attackButton.getLastPressedTime();
						final float wave = (float) Math.cos(pressedTime
								* (float) Math.PI * 2.0f);
						opacity = (wave * 0.25f) + 0.75f;
					}

				} else {
					if (boosting) {
						if (mJetSprite != null) {
							mJetSprite.setVisible(true);
						}

						if (Math.abs(velocityX) < 100.0f && velocityY > 10.0f) {
							mSprite.playAnimation(PlayerAnimations.BOOST_UP
									.ordinal());
						} else if (Math.abs(velocityX) > 300.0f) {
							mSprite.playAnimation(PlayerAnimations.BOOST_MOVE_FAST
									.ordinal());
						} else {
							mSprite.playAnimation(PlayerAnimations.BOOST_MOVE
									.ordinal());
						}
					} else {

						if (Utils.close(velocityX, 0.0f, 1.0f)) {
							mSprite.playAnimation(PlayerAnimations.IDLE
									.ordinal());
						} else if (Math.abs(velocityX) > 300.0f) {
							mSprite.playAnimation(PlayerAnimations.MOVE_FAST
									.ordinal());
						} else {
							mSprite.playAnimation(PlayerAnimations.MOVE
									.ordinal());
						}
					}

				}
			} else if (currentAction == ActionType.ATTACK) {
				mSprite.playAnimation(PlayerAnimations.STOMP.ordinal());
				if (touchingGround && gameTime > mLandThumpDelay) {
					if (mLandThump != null && sound != null) {
						// modulate the sound slightly to avoid sounding too
						// similar
						sound.play(mLandThump, false,
								SoundSystem.PRIORITY_HIGH, 1.0f,
								(float) (Math.random() * 0.5f) + 0.75f);
						mLandThumpDelay = gameTime + LAND_THUMP_DELAY;
					}
				}
			} else if (currentAction == ActionType.HIT_REACT) {
				mSprite.playAnimation(PlayerAnimations.HIT_REACT.ordinal());

				if (velocityX > 0.0f) {
					parentObject.facingDirection.x = -1.0f;
				} else if (velocityX < 0.0f) {
					parentObject.facingDirection.x = 1.0f;
				}

				if (mSparksSprite != null) {
					mSparksSprite.setVisible(true);
				}
			} else if (currentAction == ActionType.DEATH) {
				if (mPreviousAction != currentAction) {
					if (mExplosionSound != null) {
						sound.play(mExplosionSound, false,
								SoundSystem.PRIORITY_NORMAL);
					}
					// by default, explode when hit with the DEATH hit type.
					boolean explodingDeath = parentObject.lastReceivedHitType == HitType.DEATH;
					// or if touching a death tile.
					HotSpotSystem hotSpot = sSystemRegistry.hotSpotSystem;
					if (hotSpot != null) {
						// TODO: HACK! Unify all this code.
						if (hotSpot.getHotSpot(
								parentObject.getCenteredPositionX(),
								parentObject.getPosition().y + 10.0f) == HotSpotSystem.HotSpotType.DIE) {
							explodingDeath = true;
						}
					}
					if (explodingDeath) {
						mExplodingDeath = true;
						GameObjectFactory factory = sSystemRegistry.gameObjectFactory;
						GameObjectManager manager = sSystemRegistry.gameObjectManager;
						if (factory != null && manager != null) {
							GameObject explosion = factory
									.spawnEffectExplosionGiant(
											parentObject.getPosition().x,
											parentObject.getPosition().y);
							if (explosion != null) {
								manager.add(explosion);
							}
						}
					} else {
						mSprite.playAnimation(PlayerAnimations.DEATH.ordinal());
						mExplodingDeath = false;
					}

					mFlickerTimeRemaining = 0.0f;
					if (mSparksSprite != null) {
						if (!mSprite.animationFinished()) {
							mSparksSprite.setVisible(true);
						}
					}
				}
				if (mExplodingDeath) {
					visible = false;
				}
			} else if (currentAction == ActionType.FROZEN) {
				mSprite.playAnimation(PlayerAnimations.FROZEN.ordinal());
			}

			if (mFlickerTimeRemaining > 0.0f) {
				mFlickerTimeRemaining -= timeDelta;
				if (gameTime > mLastFlickerTime + FLICKER_INTERVAL) {
					mLastFlickerTime = gameTime;
					mFlickerOn = !mFlickerOn;
				}
				mSprite.setVisible(mFlickerOn);
				if (mJetSprite != null && mJetSprite.getVisible()) {
					mJetSprite.setVisible(mFlickerOn);
				}
			} else {
				mSprite.setVisible(visible);
				mSprite.setOpacity(opacity);
			}

			mPreviousAction = currentAction;
		}
	}

	public void setSprite(SpriteComponent sprite) {
		mSprite = sprite;
	}

	public void setJetSprite(SpriteComponent sprite) {
		mJetSprite = sprite;
	}

	public void setSparksSprite(SpriteComponent sprite) {
		mSparksSprite = sprite;
	}

	public void setPlayer(PlayerComponent player) {
		mPlayer = player;
	}

	public final void setDamageSwap(ChangeComponentsComponent damageSwap) {
		mDamageSwap = damageSwap;
	}

	public void setLandThump(Sound land) {
		mLandThump = land;
	}

	public void setRocketSound(Sound sound) {
		mRocketSound = sound;
	}

	public void setRubySounds(Sound one, Sound two, Sound three) {
		mRubySound1 = one;
		mRubySound2 = two;
		mRubySound3 = three;
	}

	public void setInventory(InventoryComponent inventory) {
		mInventory = inventory;
	}

	public void setExplosionSound(Sound sound) {
		mExplosionSound = sound;
	}
}
